#pragma once

//////////////////////////////////////////////////////////////////////////////////////////
// Typedefs
typedef unsigned char  UInt8;
typedef unsigned short UInt16;
typedef unsigned int   UInt32;

typedef char  Int8;
typedef short Int16;
typedef int   Int32;

// Word size
typedef UInt32 UInt;

// Word size
typedef Int32 Int;

typedef UInt16 Color;

//////////////////////////////////////////////////////////////////////////////////////////
// Structures


static const unsigned int asciiFontOffset = 32;
static const char asciiFontLetters[] = {' ', '!', '"', '#', '$', '%', '&',  '\'', '(', ')', '*', 
'+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8',  '9', ':', ';', '<',
'=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',  'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',  'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 0}; // 95 letters

struct RGB16Bitmap
{
	UInt16 width;
	UInt16 height;
	Color data[];
};

struct MonochromeBitmap
{
	UInt16 width;
	UInt16 height;
	Color data[];
};

struct ASCIIFont
{
	UInt8 letterHeight;
	UInt8 letterWidths[(sizeof(asciiFontLetters)-1)];
	UInt16 letterOffsets[(sizeof(asciiFontLetters)-1)];
	struct MonochromeBitmap bitmap;
};


//////////////////////////////////////////////////////////////////////////////////////////
// Enums/Definitions/Constants

#define M3SCREEN_WIDTH 240
#define M3SCREEN_HEIGHT 160

///////////////////////////////////////////
// Colors
enum COLOR
{
	RED    = 0x001F,
	GREEN  = 0x03E0,
	BLUE   = 0x7C00,
	BLACK  = 0x0000,
	LIME   = 0x03E0,
	YELLOW = 0x03FF,
	MAG    = 0x7C1F,
	CYAN   = 0x7FE0,
	WHITE  = 0x7FFF
};

//////////////////////////////////////////////////////////////////////////////////////////
// Macros & Inline Functions

// r/g/b - [0 - 31] | [0 - 0x1F]
#define COLOR(r, g, b) ((Color)(((Color)(b) << 10) | ((Color)(g) << 5) | ((Color)(r))))

///////////////////////////////////////////
// Video Mode 3

#define M3PixelAt(x, y) ((Color*)0x06000000)[(x)+(y)*M3SCREEN_WIDTH]
#define M3PPixelAt(x, y) ((Color*)0x06000000) + ((x)+(y)*M3SCREEN_WIDTH)

inline void M3DrawPixel(UInt x, UInt y, Color c)
{ M3PixelAt(x, y) = c; }

inline void M3DrawRect(Int x, Int y, UInt w, UInt h, Color c)
{
	Color* p = M3PPixelAt(x, y) + w;
	UInt32 dc = (UInt32)c << 16 | (UInt32)c;

	if ((w - x) & 1)
	{
		for (y = 0; y < h; ++y, p += M3SCREEN_WIDTH)
		{
			for (x = 0, p -= w; x < w; ++x, ++p)
			{
				if (x + 1 < w)
				{
					*((UInt32*)p) = dc;
					++p;
					++x;
				}
				else
				{
					*p = c;
				}
			}
		}
	}
	else
	{
		for (y = 0; y < h; ++y, p += M3SCREEN_WIDTH)
			for (x = 0, p -= w; x < w; x += 2, p += 2)
				*((UInt*)p) = dc;
	}
}

inline void M3DrawBitmap(const struct RGB16Bitmap* b,
						 Int x,
						 Int y)
{
	Color* p = M3PPixelAt(x, y) + b->width;
	UInt y2, x2;

	for (y2 = 0; y2 < b->height; ++y2, p += M3SCREEN_WIDTH)
	{
		if (y2 + y >= M3SCREEN_HEIGHT)
			continue;

		for (x2 = 0, p -= b->width; x2 < b->width; ++x2, ++p)
		{
			if (x2 + x >= M3SCREEN_WIDTH)
				continue;

			*p = b->data[x2 + y2*b->width];
		}
	}
}

// Returns the width of the letter
inline UInt M3DrawASCIILetter(UInt shiftedAscii,
							  const struct ASCIIFont* font,
							  Int x,
							  Int y)
{
	UInt offset = font->letterOffsets[shiftedAscii];
	UInt letterWidth = font->letterWidths[shiftedAscii];
	Color* p = M3PPixelAt(x, y) + letterWidth;
	Color bc;

	for (y = 0; y < font->letterHeight; ++y, p += M3SCREEN_WIDTH)
	{
		for (x = 0, p -= letterWidth; x < letterWidth; ++x, ++p)
		{
			bc = (font->bitmap.data + offset)[x + y*font->bitmap.width];
			if (bc != BLACK)
				*p = bc;
		}
	}
	return letterWidth;
}

inline void M3DrawASCII(const Int8* ascii,
						Int x,
						Int y,
						const struct ASCIIFont* font)
{
	Int shiftedAscii;
	Int x2 = x;
	UInt mw = font->letterWidths['m' - asciiFontOffset];

	for (; *ascii != '\0'; ++ascii)
	{
		shiftedAscii = *ascii - asciiFontOffset;
		if (shiftedAscii >= 0 && shiftedAscii < sizeof(asciiFontLetters) - 1)
		{

			x2 += M3DrawASCIILetter
				(
					shiftedAscii,
					font,
					x2,
					y
				);
		}
		else if (shiftedAscii == ('\n' - asciiFontOffset))
		{
			y += font->letterHeight;
			x2 = x;
		}
		else
		{
			x2 += mw;
		}
	}
}
